﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Xml;
using System.Xml.Linq;
using System.Xml.Schema;

namespace RationalXml
{
	public abstract class XmlBase : IXmlBase
	{
        /// <summary>
        /// We need namespaces for everything
        /// </summary>
        public virtual XNamespace Ns { get { return string.Empty; } }
	
        /// <summary>
        /// A dictionary of fast constructors, to create XmlBase derived classes by name
        /// </summary>
        private static readonly Dictionary<string, Func<XmlBase>> Providers = new Dictionary<string, Func<XmlBase>>();

        /// <summary>
        /// Creates a dictionary of fast constructors for every class that implements XmlBase
        /// </summary>
        static XmlBase()
		{
            //get all our IXmlBase files via reflection
            IEnumerable<Type> types = Assembly.GetExecutingAssembly().GetTypes().Where(t => t.GetInterfaces().Contains(typeof(IXmlBase)) && !t.IsAbstract);
			foreach (Type type in types)
			{
				ConstructorInfo ctor = type.GetConstructor(new Type[0]);
				if (ctor == null) continue;
                //make a NewExpression that calls the ctor with the args we just created
                NewExpression newExp = Expression.New(ctor);
                //create a lambda with the New Expression as body and our param object[] as arg
                LambdaExpression lambda = Expression.Lambda(typeof(Func<XmlBase>), newExp);
                //compile it
                Func<XmlBase> compiled = (Func<XmlBase>)lambda.Compile();
                //add it to the dictionary
                Providers.Add(type.Name, compiled);
			}
		}

        /// <summary>
        /// Converts an XElement to an object, by name, relying on the dictionary created in the static constructor
        /// </summary>
        public static IXmlBase FromXElement(XElement xElement)
		{
			try
			{
			    string localName = xElement.Name.LocalName;
                if (!Providers.ContainsKey(localName)) return null;
                IXmlBase result = Providers[localName].Invoke();
                result.ReadXml(xElement);
			    return result;
			}
			catch(Exception e)
			{
				Console.WriteLine(e.Message);
			    return null;
			}
		}

        /// <summary>
        /// Converts an XElement to an object, by type
        /// </summary>
		public static T Parse<T>(XElement xElement) where T : XmlBase, new()
		{
			T result = new T();
			result.ReadXml(xElement);
			return result;
		}

        /// <summary>
        /// Required by IXmlSeriazable... not generally used
        /// </summary>
		public virtual XmlSchema GetSchema()
		{
			return null;
		}

        /// <summary>
        /// Required by IXmlSeriazable... but we'd rather use XElements
        /// </summary>
        public virtual void ReadXml(XmlReader reader)
		{
			ReadXml(XElement.Parse(reader.ReadOuterXml()));
		}

        /// <summary>
        /// Required by IXmlSeriazable... but we'd rather use XElements
        /// </summary>
        public virtual void WriteXml(XmlWriter writer)
		{
			//// we would use this code if XmlSerializer didn't automatically wrap classes
			//WriteXml().WriteTo(writer);

			// We have to treat root elements as different creatures 
			// if we're using an XmlSerializer, we only write their content
			// in theory, we should only be doing this to root objects, so we'll ignore the above option
			foreach (XElement e in WriteXml().Elements()) e.WriteTo(writer);
		}

        /// <summary>
        /// Override with custom serialization logic using XElements
        /// </summary>
		public virtual void ReadXml(XElement xElement)
		{
			throw new NotImplementedException();
		}

        /// <summary>
        /// Override with custom deserialization logic using XElements
        /// </summary>
		public virtual XElement WriteXml()
		{
			throw new NotImplementedException();
		}
	}
}
